/* 
 * shamefully copy from AnFetion.
 * more see: http://code.google.com/p/anfetion/
 */
package person.bangbang.im.Androidgin.Fetion;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;

import person.bangbang.im.Androidgin.Util.Log;



public class TcpSendAndReceive extends FetionBase {
    private static final String TAG = ListenThread.class.getSimpleName();

    private static TcpSendAndReceive tcpconn = null;
    private static Socket socket;

    private String sipcproxy_address;
    private int sipcproxy_port;
    private boolean closing;
    private Object lockNotify = new Object();
    public ArrayList<IConnectionClosedHandler> connectionClosedNotifier = new ArrayList<IConnectionClosedHandler>();
    public ArrayList<IPackageReceivedHandler> packageReceivedQueue = new ArrayList<IPackageReceivedHandler>();

    public boolean isClosing() {
        return closing;
    }

    private ListenThread connectThread;
    private OutputStream outputStream;

    public String getLocalAddress() {
        return socket.isConnected() ? socket.getLocalAddress().toString() : null;
    }

    public int getLocalPort() {
        return socket.isConnected() ? socket.getLocalPort() : 0;
    }

    private TcpSendAndReceive(String sipcproxy_address, int sipcproxy_port) {
        this.sipcproxy_address = sipcproxy_address;
        this.sipcproxy_port = sipcproxy_port;
    }

    public static TcpSendAndReceive getInstance(String sipcproxy_address, int sipcproxy_port) {
        if (tcpconn == null) {
            tcpconn = new TcpSendAndReceive(sipcproxy_address, sipcproxy_port);
        }
        return tcpconn;
    }

    public void setSocketTimeout(int millionSeconds) {
        try {
            socket.setSoTimeout(millionSeconds);
        } catch (SocketException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public boolean execute() {

        closing = false;
        onStatusChanged(StatusDefine.SSIAPPSIGNINIED, StatusDefine.CONNECTINGREMOTE);
        try {
            InetAddress address = InetAddress.getByName(sipcproxy_address);
            // TODO 没法设置任何socks的proxy，估计是sdk本身的问题...... 一旦设置，会导致dalvik的退出
            // socket = new Socket(address, sipcproxy_port);
            // Proxy p = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("221.130.45.208", 8889));
            // socket = new Socket(p);
            socket = new Socket(Proxy.NO_PROXY);
            InetSocketAddress sa2 = new InetSocketAddress(address, sipcproxy_port);
            socket.connect(sa2);
            socket.setSoTimeout(0);// TODO 超时时间。在登录成功后，应修改为MakeActive的间隔(暂时设为0，将来还需要处理)
            outputStream = socket.getOutputStream();
            onStatusChanged(StatusDefine.CONNECTINGREMOTE, StatusDefine.CONNECTEDTOREMOTE);
            connectThread = new ListenThread();
            connectThread.start();
            return true;
        } catch (UnknownHostException e1) {
            e1.printStackTrace();
            if (!closing) {
                onSetText("Parse remote addr error.");
                onStatusChanged(StatusDefine.CONNECTINGREMOTE, StatusDefine.SSIAPPSIGNINIED);
            }
            return false;

        } catch (IOException e) {
            e.printStackTrace();
            if (!closing) {
                onSetText("Connect remote server error.");
                onStatusChanged(StatusDefine.CONNECTINGREMOTE, StatusDefine.SSIAPPSIGNINIED);
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            if (!closing) {
                onSetText("Connect remote server error.");
                onStatusChanged(StatusDefine.CONNECTINGREMOTE, StatusDefine.SSIAPPSIGNINIED);
            }
            return false;
        }
    }

    public void close() {
        Log.i(TAG, "close!");
        closing = true;
        connectionClosedNotifier.clear();
        packageReceivedQueue.clear();
        try {
            socket.shutdownInput();
            socket.shutdownOutput();
            socket.close();
        } catch (Exception e) {
        }
        try {
            connectThread.interrupt();
        } catch (Exception e) {
        }
        tcpconn = null;
    }


    public void addConnectionClosedNotifier(IConnectionClosedHandler obj) {
        this.connectionClosedNotifier.add(obj);
    }

    public void removeConnectionClosedNotifier(IConnectionClosedHandler obj) {
        this.connectionClosedNotifier.remove(obj);
    }

    private void onConnectionClosed() {
        if (!this.closing) {
            for (int i = 0; i < this.connectionClosedNotifier.size(); i++) {
                this.connectionClosedNotifier.get(i).ConnectionClosed(this);
            }
            // this.close();
            // MiscUtil.notifyDisconnect(null);
            return;
        }
    }



    public void addPackageReceivedQueue(IPackageReceivedHandler obj) {
        synchronized (lockNotify) {
            this.packageReceivedQueue.add(obj);
        }
    }

    public void removePackageReceivedQueue(IPackageReceivedHandler obj) {
        synchronized (lockNotify) {
            this.packageReceivedQueue.remove(obj);
        }
    }

    private void onPackageReceived(FetionSIPC obj) {
        synchronized (lockNotify) {
            for (int i = 0; i < this.packageReceivedQueue.size(); i++) {
                this.packageReceivedQueue.get(i).received(obj);
            }
        }
    }

    public boolean SendSIPC(FetionSIPC data) {
        try {
            if (Settings.DEBUG) Log.d(this.getClass().getSimpleName(), "read to send");
            if (Settings.DEBUG) Log.d(this.getClass().getSimpleName(), data.toString());
            byte[] tmp = data.toString().getBytes("UTF-8");
            this.outputStream.write(tmp);
            if (Settings.DEBUG) Log.d(this.getClass().getSimpleName(), "send");
            return true;

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (Settings.DEBUG) Log.d(this.getClass().getSimpleName(), "send failer");
        return false;
    }

    // public boolean MakeZeroActive()
    // {
    // try {
    // if(reader != null)
    // {
    // reader.WaitingAZeroByte = true;
    // this.outputStream.write(new byte[] {0});
    // FrameWork.synchronizedwait(reader.WaitingAZeroByte, 5000);
    // return true;
    // }
    // return false;
    // } catch (IOException e) {
    // e.printStackTrace();
    // return false;
    // }
    //		
    // }
    FetionReader reader;

    protected class ListenThread extends Thread {
        @Override
        public void run() {
            try {
                InputStream inStream = socket.getInputStream();
                reader = new FetionReader(inStream);
                while (!ListenThread.interrupted()) {
                    if (Settings.DEBUG) Log.d(TAG, "begin read:");
                    FetionSIPC sipc = reader.ReadSIPCPackage();
                    if (sipc == null) {
                        if (!closing) {
                            Log.w(TAG, "null package received");
                            onSetText("Error occured while getting package.");
                            onStatusChanged(StatusDefine.LOGINED, StatusDefine.CONNECTEDTOREMOTE);
                            onConnectionClosed();
                        }
                        return;
                    }
                    if (Settings.DEBUG) Log.d(TAG, "packagereceived:");
                    if (Settings.DEBUG) Log.d(TAG, sipc.toString());
                    if (interrupted()) break;
                    synchronized (lockNotify) {
                        onPackageReceived(sipc);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (!closing) {
                onSetText("Error occured while getting package or connecting.");
                onStatusChanged(StatusDefine.LOGINED, StatusDefine.CONNECTEDTOREMOTE);
                onConnectionClosed();
            }
            return;
        }

    }

    public interface IConnectionClosedHandler {
        public interface IPackageReceivedHandler {
		    public boolean received(FetionSIPC sipc);
		}

		public boolean ConnectionClosed(TcpSendAndReceive sender);
    }
    
    public interface IPackageReceivedHandler {
        public boolean received(FetionSIPC sipc);
    }
}
